﻿using Koala.Pro.Common;
using Koala.Pro.Core;
using Koala.Pro.Core.Dto;
using SqlSugar;
using System.Data;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;

namespace Koala.Pro.Services
{
    public abstract class BaseCrudService<TEntity, TKey, TGetListInput> : IMyCrudService<TEntity, TKey, TGetListInput>, IColumnsInterface where TEntity : class, IEntity<TKey>, new() where TGetListInput : IPagedAndSortedResultRequest
    {
        protected DbContext dbContext;

        protected IAdo Ado => dbContext.Client.Ado;

        protected SimpleClient<TEntity> EntityDb => dbContext.Client.GetSimpleClient<TEntity>();

        protected ISqlSugarClient Client => dbContext.Client;

        public string DbConfigName { get; internal set; }

        public IApiUserSession CurrentApiUser { get; set; } = NullApiUserSession.Instance;

        protected SimpleClient<T> GetEntity<T>() where T : class, new()
        {
            return Client.GetSimpleClient<T>();
        }

        public BaseCrudService()
        {
            dbContext = new DbContext();
            if (ServiceLocator.SerivcePovider != null)
            {
                CurrentApiUser = ServiceLocator.GetService<IApiUserSession>();
            }
        }

        public virtual void SetDbConfigName(string dbConfigName)
        {
            DbConfigName = dbConfigName;
            dbContext.Init(dbConfigName);
        }

        public virtual async Task<ListResultDto<TEntity>> GetAllAsync()
        {
            ISugarQueryable<TEntity> query = EntityDb.AsQueryable();
            query = ApplyDefaultSorting(query);
            List<TEntity> items = await query.ToListAsync();
            return new ListResultDto<TEntity>
            {
                Items = items
            };
        }

        public virtual async Task<ListResultDto<TEntity>> GetAllAsync(Expression<Func<TEntity, bool>> input, string orderBy = null)
        {
            ISugarQueryable<TEntity> query = EntityDb.AsQueryable().Where(input);
            query = ApplySorting(query, orderBy);
            List<TEntity> items = await query.ToListAsync();
            return new ListResultDto<TEntity>
            {
                Items = items
            };
        }

        public virtual async Task<ListResultDto<TEntity>> GetAllByIdsAsync(IEnumerable<TKey> input)
        {
            ISugarQueryable<TEntity> query = EntityDb.AsQueryable().In<IEnumerable<TKey>>(input);
            query = ApplyDefaultSorting(query);
            List<TEntity> items = await query.ToListAsync();
            return new ListResultDto<TEntity>
            {
                Items = items
            };
        }

        public virtual async Task<PagedResultDto<TEntity>> GetListAsync(TGetListInput input)
        {
            ISugarQueryable<TEntity> query = CreateFilteredQueryAsync(input);
            int totalCount = await query.CountAsync();
            query = ApplySorting(query, input);
            query = ApplyPaging(query, input);
            return new PagedResultDto<TEntity>(totalCount, await query.ToListAsync());
        }

        public virtual async Task<PagedResultDto<TEntity>> GetListByFilterAsync(PagedSortedAndFilteredInputDto input)
        {
            ISugarQueryable<TEntity> query = CreateFilteredQueryAsync(input.Filter);
            int totalCount = await query.CountAsync();
            query = ApplySorting(query, input);
            query = query.Skip(input.SkipCount).Take(input.MaxResultCount);
            return new PagedResultDto<TEntity>(totalCount, await query.ToListAsync());
        }

        public virtual async Task<long> CountAsync(TGetListInput input)
        {
            return await CreateFilteredQueryAsync(input).CountAsync();
        }

        public virtual async Task<long> CountAsync(Expression<Func<TEntity, bool>> input)
        {
            return await EntityDb.CountAsync(input);
        }

        public virtual async Task<TEntity> GetAsync(TKey id)
        {
            return await EntityDb.GetByIdAsync(id);
        }

        public virtual async Task<TEntity> GetFirstAsync(Expression<Func<TEntity, bool>> input, string orderBy = null)
        {
            ISugarQueryable<TEntity> query = EntityDb.AsQueryable().Where(input);
            query = ApplySorting(query, orderBy);
            return await query.FirstAsync();
        }

        public virtual async Task<bool> DeleteAsync(TEntity input)
        {
            await OnOperationLog(input, OperationLogTypeEnum.DELETE);
            return await EntityDb.DeleteAsync(input);
        }

        public virtual async Task<bool> DeleteAsync(Expression<Func<TEntity, bool>> input)
        {
            await OnOperationLog(input, OperationLogTypeEnum.DELETE);
            return await EntityDb.DeleteAsync(input);
        }

        public virtual async Task<bool> DeleteAsync(TKey id)
        {
            await OnOperationLog(id, OperationLogTypeEnum.DELETE);
            return await EntityDb.DeleteByIdAsync(id);
        }

        public virtual async Task<bool> DeleteAsync(IEnumerable<TKey> input)
        {
            await OnOperationLog(input, OperationLogTypeEnum.DELETE);
            dynamic val = input.ToArray();
            var result = await EntityDb.DeleteByIdsAsync(val);
            return result;
        }

        public virtual async Task<bool> DeleteByConditionAsync(string strWhere)
        {
            await AddOperationLog(OperationLogTypeEnum.DELETE.ToString(), strWhere);
            return await EntityDb.AsDeleteable().Where(strWhere).ExecuteCommandAsync() > 0;
        }

        public virtual async Task<bool> DeleteByConditionAsync(string strWhere, List<SugarParameter> parameters)
        {
            await AddOperationLog(OperationLogTypeEnum.DELETE.ToString(), strWhere);
            return await EntityDb.AsDeleteable().Where(strWhere, parameters).ExecuteCommandAsync() > 0;
        }

        public virtual async Task<bool> InsertAsync(TEntity input)
        {
            SetIdForGuids(input);
            await OnOperationLog(input, OperationLogTypeEnum.ADD);
            return await EntityDb.InsertAsync(input);
        }

        public virtual async Task<int> InsertReturnIdentityAsync(TEntity input)
        {
            SetIdForGuids(input);
            await OnOperationLog(input, OperationLogTypeEnum.ADD);
            return await EntityDb.InsertReturnIdentityAsync(input);
        }

        public virtual async Task<long> InsertReturnBigIdentityAsync(TEntity input)
        {
            SetIdForGuids(input);
            await OnOperationLog(input, OperationLogTypeEnum.ADD);
            return await EntityDb.InsertReturnBigIdentityAsync(input);
        }

        public virtual async Task<bool> InsertRangeAsync(List<TEntity> input)
        {
            input?.ForEach(SetIdForGuids);
            await OnOperationLog(input, OperationLogTypeEnum.ADD);
            return await EntityDb.InsertRangeAsync(input);
        }

        public virtual async Task<bool> UpdateAsync(TEntity input)
        {
            SetIdForGuids(input);
            await OnOperationLog(input, OperationLogTypeEnum.UPDATE);
            return await EntityDb.UpdateAsync(input);
        }

        public virtual async Task<bool> UpdateRangeAsync(List<TEntity> input)
        {
            input?.ForEach(SetIdForGuids);
            await OnOperationLog(input, OperationLogTypeEnum.UPDATE);
            return await EntityDb.UpdateRangeAsync(input);
        }

        public virtual async Task<bool> InsertOrUpdateAsync(TEntity input, TKey id)
        {
            if (id != null)
            {
                if (await EntityDb.GetByIdAsync(id) != null)
                {
                    return await UpdateAsync(input);
                }
                return await InsertAsync(input);
            }
            return await InsertAsync(input);
        }

        public virtual async Task<bool> IsExistAsync(TKey id)
        {
            return await EntityDb.IsAnyAsync((TEntity s) => s.Id.Equals(id));
        }

        public virtual async Task<bool> IsExistAsync(Expression<Func<TEntity, bool>> input)
        {
            return await EntityDb.IsAnyAsync(input);
        }

        public virtual async Task<Dictionary<string, string>> GetColumnNameAlias()
        {
            return await Task.FromResult(new Dictionary<string, string>());
        }

        public virtual Task<string> GetDisplayColumns()
        {
            return Task.FromResult("");
        }

        public virtual async Task<List<string>> GetFieldList(Expression<Func<TEntity, object>> selector, Expression<Func<TEntity, bool>> where = null)
        {
            ISugarQueryable<TEntity> sugarQueryable = EntityDb.AsQueryable();
            if (where != null)
            {
                sugarQueryable = sugarQueryable.Where(where);
            }
            ExpressionContext expressionContext = dbContext.GetExpressionContext();
            expressionContext.Resolve(selector, ResolveExpressType.FieldSingle);
            string @string = expressionContext.Result.GetString();
            return await sugarQueryable.Select<string>(@string).Distinct().ToListAsync();
        }

        public virtual ListResultDto<TEntity> SqlQuery(string sql, List<SugarParameter> parameters = null)
        {
            List<TEntity> items = Ado.SqlQuery<TEntity>(sql, parameters);
            return new ListResultDto<TEntity>
            {
                Items = items
            };
        }

        public virtual string SqlValueList(string sql, List<SugarParameter> parameters = null)
        {
            StringBuilder stringBuilder = new StringBuilder();
            using (IDataReader dataReader = Ado.GetDataReader(sql, parameters))
            {
                while (dataReader.Read())
                {
                    stringBuilder.AppendFormat("{0},", dataReader[0].ToString());
                }
            }
            return stringBuilder.ToString().Trim(new char[1] { ',' });
        }

        public virtual int SqlExecute(string sql, List<SugarParameter> parameters = null)
        {
            return Ado.ExecuteCommand(sql, parameters);
        }

        public virtual async Task<int> SqlExecuteAync(string sql, List<SugarParameter> parameters = null)
        {
            return await Ado.ExecuteCommandAsync(sql, parameters);
        }

        public virtual int StoreProcExecute(string storeProcName, List<SugarParameter> parameters = null)
        {
            return Ado.UseStoredProcedure().ExecuteCommand(storeProcName, parameters);
        }

        public virtual async Task<int> StoreProcExecuteAsync(string storeProcName, List<SugarParameter> parameters = null)
        {
            return await Ado.UseStoredProcedure().ExecuteCommandAsync(storeProcName, parameters);
        }

        public virtual DataTable SqlTable(string sql, List<SugarParameter> parameters = null)
        {
            DataTable dataTable = Ado.GetDataTable(sql, parameters);
            if (dataTable != null)
            {
                dataTable.TableName = "tableName";
            }
            return dataTable;
        }

        public virtual DataSet SqlDataSet(string sql, List<SugarParameter> parameters = null)
        {
            return Ado.GetDataSetAll(sql, parameters);
        }

        protected virtual async Task OnOperationLog(TKey id, OperationLogTypeEnum logType)
        {
            await Task.CompletedTask;
        }

        protected virtual async Task OnOperationLog(TEntity input, OperationLogTypeEnum logType)
        {
            await Task.CompletedTask;
        }

        protected virtual async Task OnOperationLog(IEnumerable<TKey> input, OperationLogTypeEnum logType)
        {
            await Task.CompletedTask;
        }

        protected virtual async Task OnOperationLog(Expression<Func<TEntity, bool>> input, OperationLogTypeEnum logType)
        {
            await Task.CompletedTask;
        }

        protected virtual async Task OnOperationLog(List<TEntity> input, OperationLogTypeEnum logType)
        {
            await Task.CompletedTask;
        }

        public virtual async Task AddOperationLog(string logType, string note)
        {
            await Task.CompletedTask;
        }

        protected virtual ISugarQueryable<TEntity> ApplySorting(ISugarQueryable<TEntity> query, object input)
        {
            if (input is ISortedResultRequest sortedResultRequest && !sortedResultRequest.Sorting.IsNullOrWhiteSpace())
            {
                return query.OrderBy(sortedResultRequest.Sorting);
            }
            if (input is ILimitedResultRequest)
            {
                return ApplyDefaultSorting(query);
            }
            return query;
        }

        protected virtual ISugarQueryable<TEntity> ApplySorting(ISugarQueryable<TEntity> query, string sorting)
        {
            if (!sorting.IsNullOrWhiteSpace())
            {
                return query.OrderBy(sorting);
            }
            return ApplyDefaultSorting(query);
        }

        protected virtual ISugarQueryable<TEntity> ApplyDefaultSorting(ISugarQueryable<TEntity> query)
        {
            if (typeof(TEntity).IsAssignableTo<IEntity<TKey>>())
            {
                return query.OrderBy((TEntity e) => e.Id);
            }
            return query.OrderBy("Id");
        }

        protected virtual ISugarQueryable<TEntity> ApplyPaging(ISugarQueryable<TEntity> query, TGetListInput input)
        {
            if (input != null)
            {
                return query.Skip(input.SkipCount).Take(input.MaxResultCount);
            }
            ILimitedResultRequest limitedResultRequest = input;
            if (limitedResultRequest != null)
            {
                return query.Take(limitedResultRequest.MaxResultCount);
            }
            return query;
        }

        protected virtual ISugarQueryable<TEntity> CreateFilteredQueryAsync(TGetListInput input)
        {
            return EntityDb.AsQueryable();
        }

        protected virtual ISugarQueryable<TEntity> CreateFilteredQueryAsync(string filter)
        {
            return EntityDb.AsQueryable();
        }

        protected virtual void SetIdForGuids(TEntity entity)
        {
            if (entity is IEntity<Guid> entity2 && entity2.Id == Guid.Empty)
            {
                SequentialGuidType sequentialGuidType = SequentialGuidType.SequentialAsString;
                switch (dbContext.DbType)
                {
                    case SqlSugar.DbType.SqlServer:
                        sequentialGuidType = SequentialGuidType.SequentialAtEnd;
                        break;
                    case SqlSugar.DbType.MySql:
                    case SqlSugar.DbType.PostgreSQL:
                        sequentialGuidType = SequentialGuidType.SequentialAsString;
                        break;
                    case SqlSugar.DbType.Oracle:
                        sequentialGuidType = SequentialGuidType.SequentialAsBinary;
                        break;
                }
                Guid sequentialGuid = GetSequentialGuid(sequentialGuidType);
                entity2.Id = sequentialGuid;
            }
            else if (entity is IEntity<string> entity3 && string.IsNullOrWhiteSpace(entity3.Id))
            {
                entity3.Id = GetSequentialGuid(SequentialGuidType.SequentialAsString).ToString();
            }
        }

        protected Guid GetSequentialGuid(SequentialGuidType sequentialGuidType)
        {
            return new SequentialGuidGenerator(new AbpSequentialGuidGeneratorOptions
            {
                DefaultSequentialGuidType = sequentialGuidType
            }).Create();
        }

        public void BeginTran()
        {
            dbContext.Client.BeginTran();
        }

        public void CommitTran()
        {
            dbContext.Client.CommitTran();
        }

        public void RollbackTran()
        {
            dbContext.Client.RollbackTran();
        }

        protected virtual void OnBeforeInsert(TEntity info)
        {
            if (info is IFullAuditEntity fullAuditEntity)
            {
                fullAuditEntity.Creator = CurrentApiUser.Id.ToString();
                fullAuditEntity.CreateTime = DateTime.Now;
                fullAuditEntity.Company_ID = CurrentApiUser.Company_ID;
                fullAuditEntity.Dept_ID = CurrentApiUser.Dept_ID;
            }
            else
            {
                SetProperty(info, "Creator", CurrentApiUser.Id);
                SetProperty(info, "CreateTime", DateTime.Now);
            }
        }

        protected virtual void OnBeforeUpdate(TEntity info)
        {
            if (info is IFullAuditEntity fullAuditEntity)
            {
                fullAuditEntity.Editor = CurrentApiUser.Id.ToString();
                fullAuditEntity.EditTime = DateTime.Now;
                fullAuditEntity.Company_ID = CurrentApiUser.Company_ID;
                fullAuditEntity.Dept_ID = CurrentApiUser.Dept_ID;
            }
            else
            {
                SetProperty(info, "Editor", CurrentApiUser.Id);
                SetProperty(info, "EditTime", DateTime.Now);
            }
        }

        private void SetProperty(TEntity obj, string name, object value)
        {
            try
            {
                PropertyInfo property = obj.GetType().GetProperty(name);
                if (property != null && value != null)
                {
                    value = Convert.ChangeType(value, property.PropertyType);
                    property.SetValue(obj, value, null);
                }
            }
            catch
            {
            }
        }
    }

}
